//
// Created by alfielin on 2021/8/20.
//
#pragma once

#include <random>
#include <chrono>
#include <stdexcept>

namespace nz {

class RNG {
private:
    std::default_random_engine generator;
    const int LOOP_LIMIT = 1000;

public:

    RNG() {
        unsigned seed = static_cast<unsigned>(std::chrono::system_clock::now().time_since_epoch().count());
        generator = std::default_random_engine(seed);
    }

    int rand_int(int low, int high) {
        return rand_int(low, high, [](int) { return true; });
    }

    template<typename UnaryPredicate>
    int rand_int(int low, int high, UnaryPredicate pred) {
        std::uniform_int_distribution<int> distribution(low, high - 1);
        int num;
        int cnt = 0;
        do {
            num = distribution(generator);
            ++cnt;
            if (cnt == LOOP_LIMIT) {
                throw std::runtime_error("Loop timeout");
            }
        } while (!pred(num));
        return num;
    }

    double rand_double(double low, double high) {
        return rand_double(low, high, [](double) { return true; });
    }

    template<typename UnaryPredicate>
    double rand_double(double low, double high, UnaryPredicate pred) {
        std::uniform_real_distribution<double> distribution(low, high - 1);
        double num;
        int cnt = 0;
        do {
            num = distribution(generator);
            ++cnt;
            if (cnt == LOOP_LIMIT) {
                throw std::runtime_error("Loop timeout");
            }
        } while (!pred(num));
        return num;

    }
};

}


